import collections
from collections import namedtuple
from dataclasses import dataclass, field
from os import name
import typing
from typing import List, NamedTuple

Card = collections.namedtuple('Card', ['rank', 'suit'])
suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)


class FrenchDeck:

    # static fields
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __repr__(self):
        return super().__repr__()

    def __init__(self, *args, **kwargs):
        # instance and private fields
        self._cards = [Card(rank, suit)
                       for suit in self.suits for rank in self.ranks]

    def __len__(self):
        """实现len()方法
        """
        return len(self._cards)

    def __getitem__(self, position):
        """python内置方法, 实现切片的重载

        Arguments:
            position {tuple}} -- 切片信息
        """
        return self._cards[position]


def spades_high(card: Card):
    # 获取到每种牌的牌号index, 即1, 2, 3, 4...J, Q, K, A(0, 1, 2, 3..., 9, 10, 11, 12)
    rank_value = FrenchDeck.ranks.index(card.rank)
    return rank_value * len(suit_values) + suit_values[card.suit]


if __name__ == "__main__":
    french_deck = FrenchDeck()

    print(french_deck[7])

    print(len(french_deck))

    print(french_deck[:10])

    for deck in french_deck:
        print(deck)

    for deck in reversed(french_deck):
        print(deck)

    for card in sorted(french_deck, key=spades_high):
        print(card)


class Coordinate:
    """
    Basic
    class model, old real fast,
    each variable metioned three times, doesn't buy us basic features
    compare does not support, need to compare each
    not support __eq__, __repr__
    """

    def __init__(self, lat, long) -> None:
        self.lat = lat
        self.long = long


# support useful __repr__, meaningful __eq__
# give two parameters, one is name, one is list field names, list of string
# or single space-delimited string
Coordinate1 = namedtuple('Coordinate1', 'lat long')

# since python3.7
CoordinateWithDefault = namedtuple(
    'Coordinate', 'lat long reference', defaults=['WGS84'])

CoordinateWithDefault(0, 0)  # lat=0 long=0 reference='WGS84'

# issubclass(Coordinate1, tuple) True

# giving keywords, since python3.6
Coordinate2 = typing.NamedTuple('Coordinate2', lat=float, long=float)


class Coordinate3(NamedTuple):
    """
    Coordinate3 same as Coordinate2, can customed, much more readable

    Args:
        NamedTuple: NamedTuple generate __init__method, not implement by class,
            params like we declared below.
    subclass of tuple
    """
    long: float
    lat: float

    def __str__(self) -> str:
        ns = 'N' if self.lat >= 0 else 'S'
        we = 'E' if self.long >= 0 else 'W'
        return f'{abs(self.lat):.lf}{ns}, {abs(self.long):.lf}{we}'


class Coordinate3WithDefault(NamedTuple):

    lat: float
    long: float
    reference: str = 'WGS84'

# not NamedTuple subclass
# issubclass(Coordinate3, typing.NamedTuple) False
# issubclass(Coordinate3, tuple) True


@dataclass(frozen=True)
class Coordinate4:
    """
     Like Coordinate3, decorators reads variable annotations and automatically
    generated methods for class.
    sub class of object
    frozen=True, when instantiateed assign new value will raise a exception
    """

    lat: float
    long: float

    def __str__(self) -> str:
        ns = 'N' if self.lat >= 0 else 'S'
        we = 'E' if self.long >= 0 else 'W'
        return f'{abs(self.lat):.1f}{ns}, {abs(self.long):.1f}{we}'


@dataclass
class ClubMember:

    name: str
    # use default_factory to create mutable default value
    guests: list = field(default_factory=list)
    # List[str] means 'a list of str', List[str] is a generic type definition
    guests_str: List[str] = field(default_factory=list)
    # default value for athlete, repr not include this field
    athlete: bool = field(default=False, repr=False)
    # wrong example
    # guests: list = []


@dataclass
class HackerClubMember(ClubMember):

    # 实例变量, 但是是可变类型, 解释了为什么example中某些操作
    # 但在一些语法检查中无法通过, 如mypy, 可以将其声明为类变量, 见hacker_club.py
    all_handles = set()

    handle: str = ''

    # post method running after __init__ method to do more works
    def __post_init__(self):
        cls = self.__class__
        if self.handle == '':
            self.handle = self.name.split()[0]
        if self.handle in cls.all_handles:
            msg = f'handle {self.handle!r} already exists'
            raise ValueError(msg)
        cls.all_handles.add(self.handle)
